﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Schedule.Support.Sql
{
	#region SqlParsePhraseFrom : SQL FRÁZE "FROM" (přehled joinovaných tabulek včetně tabulky FROM)
	/// <summary>
	/// Exaktně analyzovaná data fráze FROM ... JOIN...
	/// Obsahuje přehled tabulek a jejich zapojení do dotazu.
	/// </summary>
	public class SqlParsePhraseFrom
	{
		#region TVORBA FRÁZE FROM Z FRONTY SQL POLOŽEK
		private SqlParsePhraseFrom(SqlParseSyntaxItem syntax)
		{
			this.ParentSyntax = syntax;
			this.FromTableList = new List<SqlParseFromTable>();
		}
		/// <summary>
		/// Vizualizace fráze FROM
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			string from = "";
			foreach (SqlParseFromTable table in this.FromTableList)
				from += (from.Length == 0 ? "" : ", " + Environment.NewLine) + table.ToString();

			return from;
		}
		/// <summary>
		/// Vytvoří objekt SqlParseFromTable z dat, která přicházejí ve frontě SQL položek.
		/// Zahájí zpracování až tehdy, až najde item s klíčovým slovem FROM.
		/// </summary>
		/// <param name="sqlQueue"></param>
		/// <returns></returns>
		public static SqlParsePhraseFrom CreateFromQueue(SqlParseSyntaxItem syntax)
		{
			SqlParsePhraseFrom from = new SqlParsePhraseFrom(syntax);

			if (syntax != null)
			{
				Queue<SqlParseItem> sqlQueue = syntax.GetAllItemsQueue();
				if (sqlQueue.Count > 0)
					from._FillFromQueue(sqlQueue);
			}

			return from;
		}
		/// <summary>
		/// Naplní do sebe data z předané fronty SQL položek.
		/// Fronta může začínat čímkoliv, ale zpracování se spustí klíčovým slovem "FROM".
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueue(Queue<SqlParseItem> sqlQueue)
		{
			this.FromTableList = new List<SqlParseFromTable>();
			if (sqlQueue == null || sqlQueue.Count == 0) return;

			// Najdu slovo FROM:
			SqlParseItem.FindFirstKeywordInQueue(sqlQueue, "FROM");

			// Dokud jsou nějaká slova:
			while (sqlQueue.Count > 0)
			{
				int cntPre = sqlQueue.Count;

				SqlParseFromTable table = SqlParseFromTable.CreateFromQueue(this, sqlQueue);
				if (sqlQueue.Count == cntPre || table == null)
					break;

				this.FromTableList.Add(table);
			}
		}
		/// <summary>
		/// Odkaz na syntaktickou frázi v rámci mého SQL commandu. Tuto frázi zde exaktně analyzujeme.
		/// </summary>
		public SqlParseSyntaxItem ParentSyntax { get; private set; }
		/// <summary>
		/// Přehled tabulek, které jsou ve frázi FROM (a JOIN) použity
		/// </summary>
		public List<SqlParseFromTable> FromTableList { get; private set; }
		#endregion
		#region SLUŽBY
		/// <summary>
		/// Ve svém přehledu tabulek najde tabulku daného jména, a vrátí její alias.
		/// Pokud tabulku nenajde, vrátí null.
		/// Pokud tabulku najde, ale ta nemá alias, vrátí jméno tabulky (je použitá pod svým plným jménem).
		/// Pokud je tabulka použita vícekrát, vrací první alias.
		/// </summary>
		/// <param name="tableName"></param>
		/// <returns></returns>
		internal string FindAliasForTable(string tableName)
		{
			SqlParseFromTable table = this.FromTableList.Find(t => String.Equals(t.Source, tableName, StringComparison.OrdinalIgnoreCase));
			if (table == null) return null;

			if (table.AliasItem == null)
				return tableName;
			return table.AliasItem.Text;
		}
		#endregion
	}
	#endregion
	#region SqlParseFromTable : DATA O JEDNÉ TABULCE V RÁMCI FRÁZE "FROM" (druh zapojení, tabulka, alias, výraz ON, hinty)
	/// <summary>
	/// Data o jedné tabulce ve frázi FROM ... JOIN.
	/// </summary>
	public class SqlParseFromTable
	{
		#region TVORBA TABULKY Z FRONTY SQL POLOŽEK
		private SqlParseFromTable(SqlParsePhraseFrom parentFrom)
		{
			this.ParentFrom = parentFrom;
			KeywordItems = new List<SqlParseItem>();
			JoinHints = new List<SqlParseItem>();
			SourceItems = new List<SqlParseItem>();
			OnExpressions = new List<SqlParseItem>();
			HintItems = new List<SqlParseItem>();
		}
		/// <summary>
		/// Vizualizace tabulky
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return this.Keyword + " : " + this.Source + this.AliasText;
		}
		/// <summary>
		/// Vytvoří objekt SqlParseFromTable z dat, která přicházejí ve frontě SQL položek.
		/// Zahájí zpracování až tehdy, až najde item s klíčovým slovem FROM, LEFT, OUTER, JOIN.
		/// </summary>
		/// <param name="sqlQueue"></param>
		/// <returns></returns>
		public static SqlParseFromTable CreateFromQueue(SqlParsePhraseFrom parentFrom, Queue<SqlParseItem> sqlQueue)
		{
			if (sqlQueue == null || sqlQueue.Count == 0) return null;

			SqlParseFromTable table = new SqlParseFromTable(parentFrom);
			table._FillFromQueue(sqlQueue);

			if (!table.IsValid)
				return null;

			return table;
		}
		private static List<string> _KeyList;
		/// <summary>
		/// Naplní do sebe data z předané fronty SQL položek.
		/// Fronta může začínat čímkoliv, ale zpracování se spustí některým z klíčových slov "FROM", "LEFT", "RIGHT", "OUTER", "JOIN".
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueue(Queue<SqlParseItem> sqlQueue)
		{
			if (sqlQueue == null || sqlQueue.Count == 0) return;

			// Najdu první odpovídající klíčové slovo:
			_KeyList = new List<string>(new string[] { "FROM", "INNER", "LEFT", "FULL", "RIGHT", "OUTER", "JOIN" });
			SqlParseItem.FindFirstKeywordInQueue(sqlQueue, _KeyList);

			// Naplním jednotlivé fráze:
			this._FillFromQueueKeyword(sqlQueue);
			this._FillFromQueueSource(sqlQueue);
			this._FillFromQueueAlias(sqlQueue);
			if (this.JoinType != JoinTableType.From)
				this._FillFromQueueOnExpression(sqlQueue);       // Pokud jde o nějaký JOIN, zkusím detekovat segment ON
			this._FillFromQueueHints(sqlQueue);
		}
		/// <summary>
		/// Vrátí true, pokud daná položka SQL textu je typu SqlText, není v závorce a je to některé slovo ze seznamu klíčových slov _KeyList.
		/// </summary>
		/// <param name="sqlItem"></param>
		/// <returns></returns>
		private static bool _IsFromKeyWord(SqlParseItem sqlItem)
		{
			string sqlText = sqlItem.SqlText.ToUpper();          // SqlText.Upper (závorky, stringy => empty string)
			return (sqlItem.IsTopSqlText && _KeyList.Contains(sqlText));
		}
		/// <summary>
		/// Metoda načte z předané fronty vše, co patří do KeyWord
		/// </summary>
		/// <param name="sqlQueue"></param>
		/// <returns></returns>
		private void _FillFromQueueKeyword(Queue<SqlParseItem> sqlQueue)
		{
			_JoinDetectType joinType = _JoinDetectType.None;
			while (sqlQueue.Count > 0)
			{
				SqlParseItem.SkipVoid(sqlQueue);                 // Blank přeskočím, podívám se dál (může být na vstupu "LEFT OUTER JOIN", a měli bychom přejít přes vnitřní mezery k dalšímu klíčovému slovu):
				SqlParseItem sqlItem = sqlQueue.Peek();

				bool isFromKeyword = _IsFromKeyWord(sqlItem);
				string sqlText = sqlItem.SqlText.ToUpper();
				if (isFromKeyword)
				{	// Je to keyword: uložím si ho a dál se uvidí:
					if (joinType == _JoinDetectType.None && sqlText == "FROM")
					{	// Dosud není určen druh joinu a máme slovo FROM => je to keyword FROM: uložíme, odebereme a skončíme.
						// Za ním další keyword nemůže být.
						this.KeywordItems.Add(sqlItem);
						joinType = _JoinDetectType.From;
						sqlQueue.Dequeue();
						break;
					}
					else if (joinType == _JoinDetectType.None && sqlText == "INNER")
					{	// Začíná INNER JOIN:
						this.KeywordItems.Add(sqlItem);
						joinType = _JoinDetectType.Inner;
					}
					else if (joinType == _JoinDetectType.None && (sqlText == "LEFT" || sqlText == "RIGHT" || sqlText == "FULL"))
					{	// Začíná LEFT/RIGHT/FULL JOIN:
						this.KeywordItems.Add(sqlItem);
						joinType = _JoinDetectType.Side;
					}
					else if (joinType == _JoinDetectType.Side && sqlText == "OUTER")
					{	// V rámci LEFT/RIGHT/FULL jsme našli OUTER (jedenkrát být může):
						this.KeywordItems.Add(sqlItem);
						joinType = _JoinDetectType.SideOuter;
					}
					else if ((joinType == _JoinDetectType.None || joinType == _JoinDetectType.Inner || joinType == _JoinDetectType.Side || joinType == _JoinDetectType.SideOuter) && sqlText == "JOIN")
					{	// V rámci INNER | (LEFT/RIGHT/FULL) [OUTER] jsme našli JOIN: tím klíčová slova končí:
						this.KeywordItems.Add(sqlItem);
						joinType = _JoinDetectType.Join;
						sqlQueue.Dequeue();
						break;
					}
				}
				else if (!isFromKeyword && (joinType == _JoinDetectType.Inner || joinType == _JoinDetectType.Side || joinType == _JoinDetectType.SideOuter))
				{	// Neklíčové slovo uvnitř JOIN, po slovech INNER, LEFT, ..., OUTER ale před slovem JOIN => jde o JoinHint:
					this.JoinHints.Add(sqlItem);
					// Druh klíčového slova se tím nemění.
				}

				else
				{	// Není to keyword, není to blank: bude to něco dalšího (Source), pošlu to dál (nechám to ve frontě sqlQueue):
					break;
				}
				sqlQueue.Dequeue();
			}

			// Určit typ zapojení tabulky do dotazu (From, Join, atd):
			this._DetectJoinType();
		}
		/// <summary>
		/// Vývoj detekce joinu
		/// </summary>
		private enum _JoinDetectType { None = 1, From, Inner, Side, SideOuter, Join }
		/// <summary>
		/// Detekuje typ joinu
		/// </summary>
		private void _DetectJoinType()
		{
			string from = this.Keyword.ToUpper();
			if (from == "FROM")
				this.JoinType = JoinTableType.From;
			else if (from == "JOIN")
				this.JoinType = JoinTableType.Join;
			else if (from == "INNER JOIN")
				this.JoinType = JoinTableType.InnerJoin;
			else if (from == "LEFT JOIN")
				this.JoinType = JoinTableType.LeftJoin;
			else if (from == "LEFT OUTER JOIN")
				this.JoinType = JoinTableType.LeftOuterJoin;
			else if (from == "RIGHT JOIN")
				this.JoinType = JoinTableType.RightJoin;
			else if (from == "RIGHT OUTER JOIN")
				this.JoinType = JoinTableType.RightOuterJoin;
			else if (from == "FULL JOIN")
				this.JoinType = JoinTableType.FullJoin;
			else if (from == "FULL OUTER JOIN")
				this.JoinType = JoinTableType.FullOuterJoin;
			else
				this.JoinType = JoinTableType.Join;
		}
		/// <summary>
		/// Metoda načte zdroj (tabulka nebo subselect)
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueueSource(Queue<SqlParseItem> sqlQueue)
		{
			while (sqlQueue.Count > 0)
			{
				SqlParseItem.SkipVoid(sqlQueue);                 // Blank přeskočím, podívám se dál
				SqlParseItem sqlItem = sqlQueue.Peek();

				if (sqlItem.TextComponent == SqlParseTextComponent.BracketLeft)
				{	// Výraz v závorce = subselect, načíst závorky až do konce: "(select * from tabulka)":
					this.SourceItems.AddRange(SqlParseItem.LoadBracketFromQueue(sqlQueue));
					break;
				}
				else if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
				{	// Nejde o závorku => jde o tabulku:
					this.SourceItems.Add(sqlItem);
					if (sqlItem.Text != "#")
					{	// Pokud to NENÍ příznak Temp tabulky, pak položku odeberu a skončím.
						sqlQueue.Dequeue();
						break;
					}
					// Pokud to je #, bude následovat jméno tabulky:
				}
				sqlQueue.Dequeue();
			}
		}
		/// <summary>
		/// Metoda se pokusí detekovat alias. 
		/// Zdrojová tabulka / subselect byl načten, nyní očekáváme AS, nebo alias, nebo ON, nebo WITH.
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueueAlias(Queue<SqlParseItem> sqlQueue)
		{
			while (sqlQueue.Count > 0)
			{
				SqlParseItem.SkipVoid(sqlQueue);                 // Blank přeskočím, podívám se dál
				SqlParseItem sqlItem = sqlQueue.Peek();
				if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
				{
					string sqlText = sqlItem.SqlText.ToUpper();  // Text.ToUpper, ale ne jiné typy než SqlText

					bool isFromKeyword = _IsFromKeyWord(sqlItem);  // Klíčové slovo => skončíme.
					if (isFromKeyword)
						break;

					// Tabulka je typu JOIN, a slovo je ON: skončíme
					if (this.JoinType != JoinTableType.From && sqlText == "ON")
						break;

					if (sqlText == "WITH")                          // Je to jiné klíčové slovo: to nezpracovávám já, hned skončíme ať to řeší někdo jiný
						break;

					if (sqlText == "AS" && this.AsItem == null)     // Nepovinné slovo pro ALIAS (jen jedno)
					{
						this.AsItem = sqlItem;
						sqlQueue.Dequeue();
						continue;
					}
					this.AliasItem = sqlItem;
					sqlQueue.Dequeue();
					break;
				}
				sqlQueue.Dequeue();
			}
		}
		/// <summary>
		/// Pokud detekuje klíčové slovo ON, načte joinovací výraz
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueueOnExpression(Queue<SqlParseItem> sqlQueue)
		{
			while (sqlQueue.Count > 0)
			{
				SqlParseItem.SkipVoid(sqlQueue);                 // Blank přeskočím, podívám se dál
				SqlParseItem sqlItem = sqlQueue.Peek();
				if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
				{
					string sqlText = sqlItem.SqlText.ToUpper();  // Text.ToUpper, ale ne jiné typy než SqlText

					bool isFromKeyword = _IsFromKeyWord(sqlItem);  // Klíčové slovo => skončíme.
					if (isFromKeyword)
						break;

					if (sqlText == "WITH")                       // Je to jiné klíčové slovo: to nezpracovávám já, hned skončíme ať to řeší někdo jiný
						break;

					if (sqlText == "ON")                         // Povinné slovo pro uvození výrazu (join table alias ON expression)
					{
						this.OnItem = sqlItem;
						sqlQueue.Dequeue();
						this._FillFromQueueExpression(sqlQueue);
						break;
					}
				}
				sqlQueue.Dequeue();
			}
		}
		/// <summary>
		/// Načte join výraz, končící některým klíčovým slovem.
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueueExpression(Queue<SqlParseItem> sqlQueue)
		{
			SqlParseItem.SkipVoid(sqlQueue);                 // Blank přeskočím, a podívám se dál
			List<string> stopKeyWords = new List<string>(_KeyList);
			stopKeyWords.Add("WITH");
			this.OnExpressions.AddRange(SqlParseItem.LoadExpressionFromQueue(sqlQueue, stopKeyWords));
		}
		/// <summary>
		/// Načte hinty, pokud jsou. Frontu posune na příští klíčové slovo FROM.
		/// </summary>
		/// <param name="sqlQueue"></param>
		private void _FillFromQueueHints(Queue<SqlParseItem> sqlQueue)
		{
			while (sqlQueue.Count > 0)
			{
				SqlParseItem.SkipVoid(sqlQueue);                 // Blank přeskočím, podívám se dál
				SqlParseItem sqlItem = sqlQueue.Peek();
				if (sqlItem.TextComponent == SqlParseTextComponent.SqlText)
				{
					string sqlText = sqlItem.SqlText.ToUpper();  // Text.ToUpper, ale ne jiné typy než SqlText

					if (sqlText == "WITH")                       // Je to jiné klíčové slovo: to nezpracovávám já, hned skončíme ať to řeší někdo jiný
					{
						this.HintItems.Add(sqlItem);
						this.HintItems.AddRange(SqlParseItem.LoadExpressionFromQueue(sqlQueue, _KeyList));
						break;
					}
					else
					{
						SqlParseItem.FindFirstKeywordInQueue(sqlQueue, _KeyList);
						break;
					}
				}
				sqlQueue.Dequeue();
			}
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Příznak true, pokud je tato tabulka platná (má klíčové slovo a nějaký zdroj).
		/// </summary>
		public bool IsValid
		{
			get
			{
				if (this.KeywordItems.Count == 0) return false;
				if (this.SourceItems.Count == 0) return false;
				return true;
			}
		}
		/// <summary>
		/// Typ zapojení tabulky do dotazu (from, join, left join, atd)
		/// </summary>
		public JoinTableType JoinType { get; private set; }
		/// <summary>
		/// Klíčová slova této tabulky, například "FROM", "LEFT OUTER JOIN", "JOIN".
		/// </summary>
		public string Keyword { get { return SqlParseItem.JoinToSqlText(KeywordItems).ToUpper(); } }
		/// <summary>
		/// Zdrojová tabulka / subselect / funkce ?
		/// </summary>
		public string Source { get { return SqlParseItem.JoinToSqlText(SourceItems); } }
		/// <summary>
		/// Alias zdroje nebo plné jméno tabulky, pokud není aliasovaná. Trim, ToLower.
		/// </summary>
		public string AliasSource { get { return (this.AliasItem == null ? this.Source : this.AliasItem.Text).Trim().ToLower(); } }
		/// <summary>
		/// Alias zdroje: buď nic, nebo "alias"
		/// </summary>
		public string Alias { get { return (this.AliasItem == null ? "" : this.AliasItem.Text); } }
		/// <summary>
		/// Alias zdroje: buď nic, nebo " ... (AS: alias)"
		/// </summary>
		public string AliasText { get { return (this.AliasItem == null ? "" : " ... (AS: " + this.AliasItem.Text + ")"); } }
		/// <summary>
		/// Výraz do části ON, bez jakéhokoli přidaného textu.
		/// </summary>
		public string OnClause
		{
			get
			{
				if (this.JoinType == JoinTableType.From) return "";
				if (this.OnExpressions.Count == 0) return "";
				return SqlParseItem.JoinToSqlText(OnExpressions);
			}
		}
		/// <summary>
		/// Celá sekvence ON (...), pokud je to JOIN a je tady, jinak ""
		/// </summary>
		public string OnClauseText
		{
			get
			{
				string onClause = this.OnClause;
				if (onClause.Length == 0) return "";
				return " ... ON (" + onClause + ")";
			}
		}
		/// <summary>
		/// Reference na parent objekt, který představuje celou frázi FROM
		/// </summary>
		public SqlParsePhraseFrom ParentFrom { get; private set; }
		/// <summary>
		/// Seznam klíčových slov tabulky ("FROM", "LEFT", "OUTER", "JOIN")
		/// </summary>
		public List<SqlParseItem> KeywordItems { get; private set; }
		/// <summary>
		/// Seznam hintů do JOINu
		/// </summary>
		public List<SqlParseItem> JoinHints { get; private set; }
		/// <summary>
		/// Klíčové slovo, které představuje tabulku nebo subselect tabulky.
		/// Například "lcs.product_order", nebo subselect v položkách: "(", "select", " ", "x", " ", "from", " ", "lcs.product_order", ....)
		/// </summary>
		public List<SqlParseItem> SourceItems { get; private set; }
		/// <summary>
		/// Slovo AS, může být NULL pokud je vynecháno
		/// </summary>
		public SqlParseItem AsItem { get; private set; }
		/// <summary>
		/// Slovo, které představuje ALIAS této tabulky
		/// </summary>
		public SqlParseItem AliasItem { get; private set; }
		/// <summary>
		/// Slovo ON
		/// </summary>
		public SqlParseItem OnItem { get; private set; }
		/// <summary>
		/// Výraz, podle kterého se provádí JOIN
		/// </summary>
		public List<SqlParseItem> OnExpressions { get; private set; }
		/// <summary>
		/// Případná další slova, počínaje WITH ....
		/// </summary>
		public List<SqlParseItem> HintItems { get; private set; }
		#endregion
	}
	#endregion
	#region ENUM JoinTableType : druh zapojení tabulky do konstrukce dotazu
	/// <summary>
	/// Pozice tabulky v seznamu tabulek, from / join včetně druhu
	/// </summary>
	public enum JoinTableType
	{
		None = 1,
		From,
		Join,
		InnerJoin,
		LeftJoin,
		LeftOuterJoin,
		RightJoin,
		RightOuterJoin,
		FullJoin,
		FullOuterJoin
	}
	#endregion
	#region INTERFACE ISqlCmdWithFrom : DEKLARACE, ŽE TŘÍDA OBSAHUJE PROPERTY PhraseFrom (parsovaná fráze FROM)
	/// <summary>
	/// ISqlCmdWithFrom : Deklarace, že třída obsahuje property PhraseFrom (parsovaná fráze FROM)
	/// </summary>
	public interface ISqlCmdWithFrom
	{
		/// <summary>
		/// Parsovaná fráze FROM
		/// </summary>
		SqlParsePhraseFrom PhraseFrom { get; }
	}
	#endregion
}
